package com.javacoo.junit.generator.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import static com.javacoo.junit.generator.internal.util.messages.Messages.getString;

/**
 * 对象工厂
 * <li>此类创建生成器所需的不同对象</li>
 * @author duanyong@jccfc.com
 * @date 2020/12/31 15:44
 */
public class ObjectFactory {

    private static final List<ClassLoader> externalClassLoaders;

    static {
        externalClassLoaders = new ArrayList<>();
    }

    private ObjectFactory() {
        super();
    }

    /**
     * 清除类加载器
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 10:59
     * @return: void
     */
    public static void reset() {
        externalClassLoaders.clear();
    }

    /**
     * 将自定义类加载器添加到搜索“外部”类的类加载器集合中
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 10:59
     * @param classLoader:自定义类加载器
     * @return: void
     */
    public static synchronized void addExternalClassLoader(
            ClassLoader classLoader) {
        ObjectFactory.externalClassLoaders.add(classLoader);
    }

    /**
     * 返回从上下文类加载器或客户端提供的类加载器加载的类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 11:00
     * @param type:
     * @return: java.lang.Class<?>
     */
    public static Class<?> externalClassForName(String type)
            throws ClassNotFoundException {

        Class<?> clazz;

        for (ClassLoader classLoader : externalClassLoaders) {
            try {
                clazz = Class.forName(type, true, classLoader);
                return clazz;
            } catch (Exception e) {
                // ignore - fail safe below
            }
        }

        return internalClassForName(type);
    }
    /**
     * 创建外部对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 11:01
     * @param type:指定的类名称
     * @return: java.lang.Object
     */
    public static Object createExternalObject(String type) {
        Object answer;

        try {
            Class<?> clazz = externalClassForName(type);
            answer = clazz.getConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(getString(
                    "RuntimeError.6", type), e);
        }

        return answer;
    }
    /**
     * 创建内部对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 11:02
     * @param type:指定的类名称
     * @return: java.lang.Class<?>
     */
    public static Class<?> internalClassForName(String type)
            throws ClassNotFoundException {
        Class<?> clazz = null;

        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            clazz = Class.forName(type, true, cl);
        } catch (Exception e) {
            // ignore - failsafe below
        }

        if (clazz == null) {
            clazz = Class.forName(type, true, ObjectFactory.class.getClassLoader());
        }

        return clazz;
    }
    /**
     * 获取指定资源名称的资源路径
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 11:02
     * @param resource:指定资源名称
     * @return: java.net.URL
     */
    public static URL getResource(String resource) {
        URL url;

        for (ClassLoader classLoader : externalClassLoaders) {
            url = classLoader.getResource(resource);
            if (url != null) {
                return url;
            }
        }

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        url = cl.getResource(resource);

        if (url == null) {
            url = ObjectFactory.class.getClassLoader().getResource(resource);
        }

        return url;
    }
    /**
     * 创建内部对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 11:02
     * @param type:指定的类名称
     * @return: java.lang.Class<?>
     */
    public static Object createInternalObject(String type) {
        Object answer;

        try {
            Class<?> clazz = internalClassForName(type);

            answer = clazz.getConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(getString(
                    "RuntimeError.6", type), e);
        }

        return answer;
    }
    /**
     * 获取指定资源的InputStream
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 10:54
     * @param type:
     * @return: java.io.InputStream
     */
    public static InputStream getResourceInputStream(String type){

        InputStream inputStream;

        for (ClassLoader classLoader : externalClassLoaders) {
            try {
                inputStream = classLoader.getResourceAsStream(type);
                return inputStream;
            } catch (Exception e) {
                // ignore - fail safe below
            }
        }
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        inputStream = cl.getResourceAsStream(type);
        return inputStream;
    }

    /**
     * 获取同一路径下所有子类或接口实现类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 11:25
     * @param cls:
     * @return: java.util.List<java.lang.Class>
     */
    public static List<Class> getAllAssignedClass(Class cls) throws ClassNotFoundException, IOException {
        List<Class> classes = new ArrayList<>();
        for (Class c : getClasses(cls)) {
            if (cls.isAssignableFrom(c) && !cls.equals(c)) {
                classes.add(c);
            }
        }
        return classes;
    }

    /**
     * 取得当前类路径下的所有类
     * <li>包名一致</li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 11:23
     * @param cls:
     * @return: java.util.List<java.lang.Class<?>>
     */
    public static Set<Class<?>> getClasses(Class<?> cls){
        String pk = cls.getPackage().getName();
        //第一个class类的集合
        Set<Class<?>> classes = new LinkedHashSet<>();
        return getClassesFromPkg(pk,classes);
    }
    /**
     * 加载指定包路径下的类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/12 17:17
     * @param pkg:指定包路
     * @param classes:找到的类
     * @return: java.util.Set<java.lang.Class<?>>
     */
    public static Set<Class<?>> getClassesFromPkg(String pkg,Set<Class<?>> classes) {
        // 获取包的名字 并进行替换
        String pkgDirName = pkg.replace('.', '/');
        try {
            ClassLoader classloader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> urls = classloader.getResources(pkgDirName);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findClassesByFile(pkg, filePath, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 获取jar
                    JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    //扫描jar包文件 并添加到集合中
                    findClassesByJar(pkg, jar, classes);
                }
            }
            //如果在当前包下未找到 则继续
            // if(pkg.contains(".")){
            //     pkg = pkg.substring(0,pkg.lastIndexOf("."));
            //     classes = getClzFromPkg(pkg,classes);
            // }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }
    /**
     * 加载文件系统中的类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 10:50
     * @param pkgName: 包名
     * @param pkgPath: 路径
     * @param classes: 找到的类集合
     * @return: void
     */
    private static void findClassesByFile(String pkgName, String pkgPath, Set<Class<?>> classes) {
        // 获取此包的目录 建立一个File
        File dir = new File(pkgPath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(pathname -> pathname.isDirectory() || pathname.getName().endsWith("class"));

        if (dirfiles == null || dirfiles.length == 0) {
            return;
        }

        String className;
        Class clz;
        // 循环所有文件
        for (File f : dirfiles) {
            // 如果是目录 则继续扫描
            if (f.isDirectory()) {
                findClassesByFile(pkgName + "." + f.getName(),pkgPath + "/" + f.getName(),classes);
                continue;
            }
            // 如果是java类文件 去掉后面的.class 只留下类名
            className = f.getName();
            className = className.substring(0, className.length() - 6);

            //加载类
            clz = loadClass(pkgName + "." + className);
            // 添加到集合中去
            if (clz != null) {
                classes.add(clz);
            }
        }
    }
    /**
     * 加载jar包中的类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 10:51
     * @param pkgName: 包名
     * @param jar: JarFile对象
     * @param classes: 找到的类集合
     * @return: void
     */
    private static void findClassesByJar(String pkgName, JarFile jar, Set<Class<?>> classes) {
        String pkgDir = pkgName.replace(".", "/");
        // 从此jar包 得到一个枚举类
        Enumeration<JarEntry> entry = jar.entries();

        JarEntry jarEntry;
        String name, className;
        Class<?> claze;
        // 同样的进行循环迭代
        while (entry.hasMoreElements()) {
            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文
            jarEntry = entry.nextElement();

            name = jarEntry.getName();
            // 如果是以/开头的
            if (name.charAt(0) == '/') {
                // 获取后面的字符串
                name = name.substring(1);
            }
            if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) {
                continue;
            }
            //如果是一个.class文件 而且不是目录
            // 去掉后面的".class" 获取真正的类名
            className = name.substring(0, name.length() - 6);
            //加载类
            claze = loadClass(className.replace("/", "."));
            // 添加到集合中去
            if (claze != null) {
                classes.add(claze);
            }
        }
    }
    /**
     * 加载类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/13 10:52
     * @param fullClzName:类全路径名称
     * @return: java.lang.Class<?>
     */
    private static Class<?> loadClass(String fullClzName) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(fullClzName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}
